package com.heima.article.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.heima.article.dto.ArticleDto;
import com.heima.article.dto.ArticleHomeDto;
import com.heima.article.dto.ArticleInfoDto;
import com.heima.article.dto.ArticleStreamMessage;
import com.heima.article.entity.ApArticle;
import com.heima.article.entity.ApArticleContent;
import com.heima.article.mapper.ApArticleMapper;
import com.heima.article.service.IApArticleContentService;
import com.heima.article.service.IApArticleService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.article.task.ArticleHtmlTasks;
import com.heima.common.dto.ResponseResult;
import com.heima.common.enums.AppHttpCodeEnum;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.*;

/**
 * <p>
 * 文章信息表，存储已发布的文章 服务实现类
 * </p>
 *
 * @author lj
 * @since 2022-06-29
 */
@Slf4j
@Service
public class ApArticleServiceImpl extends ServiceImpl<ApArticleMapper, ApArticle> implements IApArticleService {

    @Autowired
    private IApArticleContentService apArticleContentService;
    @Autowired
    private ArticleHtmlTasks articleHtmlTasks;
    @Autowired
    private StringRedisTemplate redisTemplate; // extend RedisTemplate<String,String>，泛型省略

    /**
     * 保存文章
     * @param dto
     * @return
     */
    @Override
    public ResponseResult<Long> saveArticle(ArticleDto dto) {
        ApArticle apArticle = new ApArticle();
        BeanUtils.copyProperties(dto,apArticle);
        // 这里重要的是传过来的数据类型，数据的正确性，比如封面图片的格式，注意了
        if (dto.getId()==null){
            // 新增的操作:保存两张表的数据
            // 文章信息
            // ApArticle apArticle = new ApArticle();
            // BeanUtils.copyProperties(dto,apArticle);
            apArticle.setCreatedTime(new Date());
            apArticle.setId(null); // 添加，笔记保险起见
            this.save(apArticle);
            // 文章内容
            ApArticleContent apArticleContent = new ApArticleContent();
            apArticleContent.setId(null); // 添加，笔记保险起见
            apArticleContent.setArticleId(apArticle.getId());
            apArticleContent.setContent(dto.getContent());
            apArticleContentService.save(apArticleContent);
            // return ResponseResult.okResult(apArticle.getId());
        }else {
            // 修改操作
            // 文章信息
            // ApArticle apArticle = new ApArticle();
            // BeanUtils.copyProperties(dto,apArticle);
            this.updateById(apArticle);
            // 文章内容
            LambdaUpdateWrapper<ApArticleContent> updateWrapper = new LambdaUpdateWrapper();
            updateWrapper.eq(ApArticleContent::getArticleId,apArticle.getId());
            updateWrapper.set(ApArticleContent::getContent,dto.getContent());
            apArticleContentService.update(updateWrapper);
            // return ResponseResult.okResult(apArticle.getId());
        }
        // 生成静态页面
        articleHtmlTasks.createHtml(apArticle,dto.getContent());
        return ResponseResult.okResult(apArticle.getId());
    }

    /**
     * 首页加载文章列表
     * @param dto
     * @param type 列表0，下拉更新0，上滑更多1
     * @return
     */
    @Override
    public ResponseResult load(ArticleHomeDto dto, int type) {
        log.info("[App文章展示列表======>首页加载文章列表，接收参数：{}，type={}]",dto.toString(),type);
        // 接收参数判断
        if (dto==null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_REQUIRE);
        }

        // 热点数据展示，从Redis中获取数据
        // 判断是否是首页
        if (dto.getLoaddir()==1) {
            String key = "hot_article_first_page_"+dto.getChannelId();
            long start =0;
            long end = dto.getSize()-1;
            Set<String> strings = redisTemplate.boundZSetOps(key).reverseRange(start, end); // 根据分数降序查询
            List<ApArticle> articles = new ArrayList<>();
            for (String string : strings) {
                ApArticle article = JSON.parseObject(string, ApArticle.class);
                articles.add(article);
            }
            // 判断，当热点文章大于等于当前页码大小时返回数据，否则依旧在Mysql中查询
            // 先判断这个篮子够不够，不够不拿，够了再拿
            if (articles.size()>=dto.getSize()) {
                return ResponseResult.okResult(articles);
            }
        }

        // 条件构造器
        LambdaQueryWrapper<ApArticle> queryWrapper = new LambdaQueryWrapper<>();
        // 图片重复问题
        // 1.时间的判断只能大于或小于不能等于，不能等于，否则会出现什么bug？
        // 重复测试又可以了？？？2.列表展示与更新操作不能设定为同一type，否则也会出现图片重复，更改：列表0，更新1，更多2；
        // type，哪种操作
        if (type==0) {
            // 列表或更新，最新时间
            queryWrapper.gt(ApArticle::getPublishTime,dto.getMaxTime());
        }else{
            // 上滑更多
            queryWrapper.lt(ApArticle::getPublishTime,dto.getMinTime());
        }
        // 频道
        if (dto.getChannelId()!=0) {
            queryWrapper.eq(ApArticle::getChannelId,dto.getChannelId());
        }
        // 隐藏条件，下架以及删除的文章不展示，逆向思维，相等->展示true，不展示false
        queryWrapper.eq(ApArticle::getIsDown,false);
        queryWrapper.eq(ApArticle::getIsDelete,false);
        // 按照时间倒序
        queryWrapper.orderByDesc(ApArticle::getCreatedTime);
        // 分页条件
        Page<ApArticle> page = new Page<>(1,dto.getSize());
        Page<ApArticle> pageInfo = this.page(page, queryWrapper);
        return ResponseResult.okResult(pageInfo.getRecords());
    }

    /**
     * 加载文章详情
     * @param dto
     * @return
     */
    @Override
    public ResponseResult loadArticleInfo(ArticleInfoDto dto) {
        // 根据id查询文章
        ApArticle apArticle = this.getById(dto.getArticleId());
        if (apArticle == null || apArticle.getIsDelete() || apArticle.getIsDown()) {
            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST);
        }
        // 根据id查询文章内容
        LambdaQueryWrapper<ApArticleContent> query = new LambdaQueryWrapper<>();
        query.eq(ApArticleContent::getArticleId, dto.getArticleId());
        ApArticleContent articleContent = apArticleContentService.getOne(query);
        // 按照前端的格式拼接map
        Map<String, Object> map = new HashMap<>();
        map.put("config", apArticle);
        map.put("content", articleContent);
        // 返回通用的响应
        return ResponseResult.okResult(map);
    }

    /**
     * 热点文章定时任务查询
     */
    @Override
    public void compute() {
        // 思路：1.查询 2.计算权重 3.存储Redis
        LambdaQueryWrapper<ApArticle> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ApArticle::getIsDown,false);
        queryWrapper.eq(ApArticle::getIsDelete,false);
        // 发布时间用到jar包joda-time的工具类，非常好用的一个时间jar包
        // DateTime dateTime = DateTime.now().minusDays(300).toDateTime(); // 300天以内的文章
        Date minDate = DateTime.now().minusDays(300).toDate();// 300天以内的文章
        queryWrapper.ge(ApArticle::getPublishTime,minDate);
        // 查询数据
        List<ApArticle> list = this.list(queryWrapper);

        // 注意，先删除再放入
        Set<String> keys = redisTemplate.keys("hot_article_first_page_*");
        redisTemplate.delete(keys);

        for (ApArticle apArticle : list) {
            // 计算文章的分值
            Integer score = computeScore(apArticle);

            ApArticle cache = new ApArticle();
            cache.setId(apArticle.getId());
            cache.setTitle(apArticle.getTitle());
            cache.setImages(apArticle.getImages());
            cache.setAuthorId(apArticle.getAuthorId());
            cache.setAuthorName(apArticle.getAuthorName());
            cache.setLayout(apArticle.getLayout());
            cache.setStaticUrl(apArticle.getStaticUrl());
            // 固定属性放入
            String jsonString = JSON.toJSONString(cache);
            // 放入到Redis中，数据结构 Zset
            // 推荐文章
            redisTemplate.boundZSetOps("hot_article_first_page_0").add(jsonString,score);
            // 各个频道上推荐文章
            redisTemplate.boundZSetOps("hot_article_first_page_"+apArticle.getChannelId()).add(jsonString,score);
        }
    }

    /**
     * 计算文章的分值
     * @param apArticle
     * @return
     */
    private Integer computeScore(ApArticle apArticle) {
        int score = 0;
        // 阅读 *1  点赞 *3  评论 *5  收藏 *8
        if (apArticle.getViews() != null) {
            score += apArticle.getViews() * 1;
        }
        if (apArticle.getLikes() != null) {
            score += apArticle.getLikes() * 3;
        }
        if (apArticle.getComment() != null) {
            score += apArticle.getComment() * 5;
        }
        if (apArticle.getCollection() != null) {
            score += apArticle.getCollection() * 8;
        }
        return score;
    }

    /**
     * 热点文章实时任务查询
     * @param message
     */
    @Override
    public void updateHotArticle(ArticleStreamMessage message) {
        // 重新计算文章的热度值
        Long articleId = message.getArticleId();
        ApArticle article = this.getById(articleId);
        // 计算文章当日的分值*3
        int score = computeTodayScore(message);

        ApArticle cache = new ApArticle();
        cache.setId(article.getId());
        cache.setTitle(article.getTitle());
        cache.setImages(article.getImages());
        cache.setAuthorId(article.getAuthorId());
        cache.setLayout(article.getLayout());
        cache.setAuthorName(article.getAuthorName());
        cache.setStaticUrl(article.getStaticUrl());

        // 更新redis中的数据
        // String key = "hot_article_first_page_" + dto.getChannelId();
        Double scoreBefore = redisTemplate.boundZSetOps("hot_article_first_page_0").score(JSON.toJSONString(cache));
        if(scoreBefore!=null){
            redisTemplate.boundZSetOps("hot_article_first_page_0").add(JSON.toJSONString(cache),scoreBefore+score);
            redisTemplate.boundZSetOps("hot_article_first_page_"+article.getChannelId()).add(JSON.toJSONString(cache),scoreBefore+score);
        }else{
            redisTemplate.boundZSetOps("hot_article_first_page_0").add(JSON.toJSONString(cache),score);
            redisTemplate.boundZSetOps("hot_article_first_page_"+article.getChannelId()).add(JSON.toJSONString(cache),score);
        }

        // 更新mysql中ApArticle中的阅读数、点赞数、评论数、收藏数
        LambdaUpdateWrapper<ApArticle> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(ApArticle::getId,message.getArticleId());
        updateWrapper.setSql("views=views+"+message.getView());
        updateWrapper.setSql("likes=likes+"+message.getLike());
        updateWrapper.setSql("comment=comment+"+message.getComment());
        updateWrapper.setSql("collection=collection+"+message.getCollect());
        this.update(updateWrapper);
    }

    /**
     * 计算文章当日的分值*3
     * @param message
     * @return
     */
    private int computeTodayScore(ArticleStreamMessage message) {
        int score = 0;
        score += message.getView() * 1 * 3;
        score += message.getLike() * 3 * 3;
        score += message.getComment() * 5 * 3;
        score += message.getCollect() * 8 * 3;
        return score;
    }


}
